package com.lib.okhttp;

import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;

import com.lib.common.bean.BaseResp;
import com.lib.okhttp.annotation.CacheType;
import com.lib.okhttp.annotation.ContentType;
import com.lib.okhttp.reqbody.FileRequestBody;
import com.lib.utils.CloseUtils;
import com.lib.utils.FileUtils;
import com.lib.utils.JSONUtils;
import com.lib.utils.NetworkUtils;
import com.lib.utils.ObjPoolUtils;
import com.lib.utils.XLog;
import com.lib.utils.XXTEA;

import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import okhttp3.Cache;
import okhttp3.CacheControl;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;

final public class NetClient {
    public static final String MEDIA_TYPE_TEXT = "text";
    public static final String MEDIA_TYPE_IMG = "image";
    public static final String MEDIA_TYPE_AUDIO = "audio";
    public static final String MEDIA_TYPE_VIDEO = "video";
    public static final String MEDIA_TYPE_FILE = "file";

    private InnerHandler mHandler;

    public interface IDataChangeListener {

        void onDataChanged(BaseResp baseResp);

        default void onProgressChanged(Integer progress) {
        }
    }

    public NetClient(IDataChangeListener listener) {
        mHandler = new InnerHandler(listener);
    }

    public static void addCommonToBuilder(Request.Builder builder) {

//            HashMap<Object, Object> map = new HashMap<>();
//            map.put("platform", "android");
//            map.put("sdk_int", String.valueOf(Build.VERSION.SDK_INT));
//            map.put("manufacturer", Build.MANUFACTURER.toLowerCase());
//            map.put("model", Build.MODEL.toLowerCase());
//            map.put("release_version", Build.VERSION.RELEASE.toLowerCase());
//            map.put("application_id", BuildConfig.APPLICATION_ID);
//            map.put("version_name", BuildConfig.VERSION_NAME);
//            map.put("version_code", String.valueOf(BuildConfig.VERSION_CODE));
//            map.put("timestamp", String.valueOf(System.currentTimeMillis()));
//            map.add("location", LocationInfoManager.ADDRESS_STR);
//            String deviceInfo = JSONUtils.toJSONString(map);
//
//            builder.addHeader("deviceInfo", deviceInfo);
//            builder.addHeader("Connection", "close");
//        }
    }

    /**
     * 获取默认的OkHttpClient实例
     */
    public static OkHttpClient getOkHttpClient() {
        return Inner.sOkHttpClient;
    }

    /**
     * 获取上传下载文件用的OkHttpClient实例
     */
    public static OkHttpClient getFileOkHttpClient() {
        return Inner.sFileOkHttpClient;
    }

    /**
     * 创建Builder
     */
    public static Request.Builder getCommonReqBuilder() {
        Request.Builder builder = new Request.Builder();
        addCommonToBuilder(builder);

        return builder;
    }

    /**
     * @param netRequest 请求的实体类，OKHttp默认支持Get请求使用缓存的数据-FORCE_CACHE
     */
    public Call doGet(NetRequest netRequest, Class<?> cls) {
        boolean showLog = XLog.isDebug();
        String url = buildUrl(netRequest, showLog);

        Request.Builder requestBuilder = getCommonReqBuilder();/*doGet*/
        configCache(requestBuilder, netRequest);

        Request request = requestBuilder
                .url(url)
                .build();

        if (showLog) {
            printHeads(request);/*doGet*/
        }

        Call call = Inner.sOkHttpClient.newCall(request);

        call.enqueue(new MyCallBack(netRequest, mHandler, cls));
        return call;
    }

    private String buildUrl(NetRequest netRequest, boolean showLog) {
        String url = netRequest.getUrl();

        HashMap<String, String> map = netRequest.doGet().getParamFormBody();
        if (map != null && map.size() > 0) {
            Set<Map.Entry<String, String>> entrySet = map.entrySet();
            StringBuilder sb = new StringBuilder();

            for (Map.Entry<String, String> entry : entrySet) {
                String key = entry.getKey();
                String value = entry.getValue();
                if (value == null) {
                    value = "";
                    XLog.e("the value is null for key->" + key);
                }
                sb.append("&").append(key).append("=").append(value);
            }

            if (url.endsWith("?")) {
                url += sb.substring(1);
            } else {
                url += "?" + sb.substring(1);
            }

            if (showLog) {
                XLog.i("doGet: url->" + url);
            }
        }
        return url;
    }

    /**
     * @param netRequest 请求类
     */
    public Call doPost(NetRequest netRequest, Class<?> cls) {
//        if (!TextUtils.isEmpty(sVoucher)) {
//            map.put("voucher", sVoucher);
//        }

        RequestBody requestBody = buildRequestBody(netRequest);

        Request request = buildRequest(netRequest, requestBody);

        Call call = Inner.sOkHttpClient.newCall(request);

        call.enqueue(new MyCallBack(netRequest, mHandler, cls));

        return call;
    }

    /**
     * 构建请求
     *
     * @return
     */
    private Request buildRequest(NetRequest netRequest, RequestBody requestBody) {
        Request.Builder requestBuilder = getCommonReqBuilder();/*buildRequest*/
        configCache(requestBuilder, netRequest);

        // 添加请求头
        Map<String, String> headMap = netRequest.doPost().getHeadMap();
        if (headMap != null) {
            Set<Map.Entry<String, String>> entrySet = headMap.entrySet();
            for (Map.Entry<String, String> entry : entrySet) {
                String key = entry.getKey();
                String value = entry.getValue();
                if (value == null) {
                    value = "";
                    XLog.e("the value is null for key->" + key);
                }
                requestBuilder.addHeader(key, value);
            }
        }

        Request request = requestBuilder
                .url(netRequest.getUrl())
                .post(requestBody)
                .build();

        if (XLog.isDebug()) {
            printHeads(request);
        }
        return request;
    }

    /**
     * 打印请求头
     */
    private void printHeads(Request request) {
        Headers headers = request.headers();
        int size = headers.size();
        StringBuilder msg = new StringBuilder();
        for (int i = 0; i < size; i++) {
            msg.append("[").append(headers.name(i)).append(" , ").append(headers.value(i)).append("]\n");
        }
        XLog.i(msg.toString());
    }

    /**
     * 构建请求体
     */
    private RequestBody buildRequestBody(NetRequest netRequest) {
        //设置请求参数编码格式
        String requestEncoding = ";charset=" + netRequest.getRequestEncoding();

        NetRequest.Post post = netRequest.doPost();
        byte[] paramBytes = post.getParamBytes();
        if (paramBytes != null) {
            MediaType mediaType = MediaType.parse(ContentType.STREAM + requestEncoding);
            RequestBody requestBody = RequestBody.create(mediaType, paramBytes);
            return requestBody;
        }
        String paramJson = post.getParamJson();
        if (paramJson != null) {
            MediaType mediaType = MediaType.parse(ContentType.JSON + requestEncoding);
            RequestBody requestBody = RequestBody.create(mediaType, paramJson);
            return requestBody;
        }
        /* 构建GET表单 */
//        if (post.getParamForm() != null) {
//            requestBody = RequestBody.create(MediaType.parse(ContentType.FORM + requestEncoding), netRequest.getParamFormBody());
//            return requestBody;
//        }
        HashMap<String, String> map = post.getParamFormBody();
        String url = netRequest.getUrl();
        RequestBody requestBody = buildFormBody(map, url);
        return requestBody;
    }

    /**
     * 构建POST表单
     */
    private RequestBody buildFormBody(HashMap<String, String> map, String url) {
        boolean showLog = XLog.isDebug();

        FormBody.Builder builder = new FormBody.Builder();

        if (map != null && map.size() > 0) {
            StringBuilder sb = null;
            if (showLog) {
                sb = ObjPoolUtils.obtain(StringBuilder.class);
                if (sb == null) {
                    sb = new StringBuilder();
                }
            }

            Set<Map.Entry<String, String>> set = map.entrySet();
            for (Map.Entry<String, String> entry : set) {
                String key = entry.getKey();
                String value = entry.getValue();
                if (value == null) {
                    value = "";
                    XLog.e("doPost: the value of key: [" + key + "] is null");
                }
                builder.add(key, value);
                if (showLog) {
                    sb.append("\n[" + key + " , " + value + "]");
                }
            }

            if (showLog) {
                XLog.i("doPost url->" + url + sb);
            }
        }

        return builder.build();
    }

    /**
     * reqTag 请求标识
     *
     * @param reqTag
     * @param url    要上传的文件 such as "text", "image", "audio", "video".
     * @param file   文件
     */
    public Call uploadFile(String reqTag, String url, File file) {
        MediaType mediaType = MediaType.parse("multipart/form-data");
        FileRequestBody fileRequestBody = new FileRequestBody(mediaType, mHandler, file);

//        MultipartBody.Builder requestBody = new MultipartBody.Builder()
//                .setType(MultipartBody.FORM)
//                .addFormDataPart(fileType, file.getName(), fileRequestBody);

        RequestBody body = new MultipartBody.Builder()
                .addFormDataPart("file", "miYuan/userHeadImg/" + file.getPath(), fileRequestBody)
                .addFormDataPart("filePath", "/miYuan/userHeadImg/")
                .setType(MultipartBody.FORM)
                .build();

        Request request = getCommonReqBuilder()/*uploadFile*/
                .url(url)
                .post(body)
                .build();

        Call call = Inner.sFileOkHttpClient.newCall(request);

        call.enqueue(new MyCallBack(null, mHandler, null));

        return call;
    }

    /**
     * 下载文件
     *
     * @param reqTag
     * @param url    文件url
     * @param file   存储目标文件
     */
    public Call downLoadFile(String reqTag, String url, final File file, String md5) {
        Callback callback = new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                XLog.w("onFailure ->" + Log.getStackTraceString(e));
                if (mHandler == null) {
                    XLog.e("mHandler is null");
                    return;
                }
                String url = call.request().url().toString();
                BaseResp baseResp = new BaseResp(url, BaseResp.CODE_ERROR_DATA, e.toString());
                Message.obtain(mHandler, InnerHandler.MSG_DATA, baseResp).sendToTarget();
            }

            @Override
            public void onResponse(Call call, Response response) {
                XLog.i("当前线程->" + Thread.currentThread().getName());

                ResponseBody body = response.body();
                if (body == null) {
                    BaseResp baseResp = new BaseResp(url, BaseResp.CODE_ERROR_DATA, "下载失败，请稍后重试！");
                    Message.obtain(mHandler, InnerHandler.MSG_DATA, baseResp).sendToTarget();
                    return;
                }
                InputStream is = body.byteStream();

                long total = body.contentLength();
                long current = 0;

                byte[] buf = new byte[1024];
                FileOutputStream fos = null;
                try {
                    fos = new FileOutputStream(file);

                    int len;
                    while ((len = is.read(buf)) != -1) {
                        XLog.i("readLen->" + len);

                        fos.write(buf, 0, len);

                        current += len;
                        if (mHandler != null) {
                            if (current >= total) {
                                Message.obtain(mHandler, InnerHandler.MSG_PROGRESS, 100).sendToTarget();
                            } else {
                                int percentage = (int) ((1.0 * current) / total * 100);
                                Message.obtain(mHandler, InnerHandler.MSG_PROGRESS, percentage).sendToTarget();
                            }
                        }

                        /*限速用*/
                        SystemClock.sleep(100);
                    }
                    fos.flush();
                } catch (Exception e) {
                    BaseResp baseResp = new BaseResp(url, BaseResp.CODE_ERROR, "下载失败！");
                    Message.obtain(mHandler, InnerHandler.MSG_DATA, baseResp).sendToTarget();
                } finally {
                    CloseUtils.closeIOQuietly(is, fos);
                }
            }
        };
//        long startPoint = 0;
//        if (resumeBroken && file.exists()) {
//            startPoint = file.length();
//        }
        Request request = getCommonReqBuilder()/*downLoadFile*/
                .url(url)
//                .addHeader("Range", "bytes=" + startPoint + "-")
                .build();

        Call call = Inner.sFileOkHttpClient.newCall(request);

        call.enqueue(callback);

        return call;
    }

    private void configCache(Request.Builder requestBuilder, NetRequest netRequest) {
        String cacheType = netRequest.getCacheType();
        switch (cacheType) {
            case CacheType.AUTO: {
                if (NetworkUtils.isConnected(XLog.sContext)) {
                    // "noop"
//                    CacheControl cacheControl = new CacheControl.Builder()
//                            .maxAge(Integer.MAX_VALUE, TimeUnit.SECONDS)
//                            .build();
//                    requestBuilder.cacheControl(cacheControl);
                } else {
                    requestBuilder.cacheControl(CacheControl.FORCE_CACHE);
                }
            }
            break;
            case CacheType.FORCE_NETWORK: {
                requestBuilder.cacheControl(CacheControl.FORCE_NETWORK);
            }
            break;
            case CacheType.FORCE_CACHE: {
                requestBuilder.cacheControl(CacheControl.FORCE_CACHE);
//                CacheControl builder = new CacheControl.Builder().onlyIfCached().build();
//                requestBuilder.cacheControl(builder);
            }
            break;
        }
    }

    public void onDestroy() {
        if (mHandler != null) {
            mHandler.removeCallbacksAndMessages(null);
            mHandler = null;
        }
    }

    public static class InnerHandler extends Handler {
        public static final int MSG_DATA = 0;
        public static final int MSG_PROGRESS = 1;

        private WeakReference<IDataChangeListener> mWr;

        private InnerHandler(IDataChangeListener listener) {
            mWr = new WeakReference<>(listener);
        }

        @Override
        public void handleMessage(Message msg) {
            IDataChangeListener listener = mWr.get();
            if (listener == null) {
                return;
            }
            if (msg.what == MSG_PROGRESS) {
                if (listener != null) {
                    listener.onProgressChanged((Integer) msg.obj);
                }
                return;
            }
            if (listener != null) {
                listener.onDataChanged((BaseResp) msg.obj);
            }
        }
    }

    private static class MyCallBack implements Callback {
        private NetRequest mNetRequest;
        private Handler mHandler;
        private Class<?> mCls;

        MyCallBack(NetRequest netRequest, Handler a, Class<?> cls) {
            mNetRequest = netRequest;
            mHandler = a;
            mCls = cls;
        }

        @Override
        public void onFailure(Call call, IOException e) {
            XLog.e("onFailure ->" + e.toString() + "\n" + Log.getStackTraceString(e));
            if (mHandler == null) {
                XLog.e("mHandler is null");
                return;
            }
            String url = call.request().url().toString();
            BaseResp baseResp = new BaseResp(url, BaseResp.CODE_ERROR, e.toString());
            Message.obtain(mHandler, InnerHandler.MSG_DATA, baseResp).sendToTarget();
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            Response networkResponse = response.networkResponse();
            if (networkResponse != null) {
                XLog.i("have networkResponse");
            }
            Response cacheResponse = response.cacheResponse();
            if (cacheResponse != null && cacheResponse.isSuccessful()) {
                XLog.i("have cacheResponse");
            }
            if (mHandler == null) {
                XLog.e("mHandler is null");
                return;
            }
            onResp(response);
        }

        private void onResp(Response response) throws IOException {
            String url = response.request().url().toString();
            ResponseBody body = response.body();
            if (body == null) {
                BaseResp baseResp = new BaseResp(url, BaseResp.CODE_ERROR_DATA, "null");
                Message.obtain(mHandler, InnerHandler.MSG_DATA, baseResp).sendToTarget();
                return;
            }
            String string = body.string();

            String secretKey = mNetRequest.getSecretKey();
            if (!TextUtils.isEmpty(secretKey)) {
                string = XXTEA.decryptStr(string, secretKey);
            }

            if (mCls == null || mCls == String.class) {
                int code = response.code();
                BaseResp baseResp = new BaseResp(url, code, string);
                if (mNetRequest.isMain()) {
                    Message.obtain(mHandler, InnerHandler.MSG_DATA, baseResp).sendToTarget();
                } else {
                    Message message = Message.obtain();
                    message.what = InnerHandler.MSG_DATA;
                    message.obj = baseResp;
                    mHandler.handleMessage(message);
                }
                return;
            }

            BaseResp baseResp = (BaseResp) JSONUtils.parseObject(string, mCls);

            if (baseResp == null) {
                XLog.e(string + "\n执行异常，无法解析成：" + mCls.getName());

                BaseResp bean = new BaseResp(url, BaseResp.CODE_ERROR_DATA, "数据解析异常，请提交反馈");
                Message.obtain(mHandler, InnerHandler.MSG_DATA, bean).sendToTarget();
                return;
            }
            baseResp.setUrl(url);
            if (mNetRequest.isMain()) {
                Message.obtain(mHandler, InnerHandler.MSG_DATA, baseResp).sendToTarget();
            } else {
                Message message = Message.obtain();
                message.what = InnerHandler.MSG_DATA;
                message.obj = baseResp;
                mHandler.handleMessage(message);
            }
        }
    }

    private static class Inner {
        /**
         * 默认的OkHttpClient实例
         */
        public static OkHttpClient sOkHttpClient = getOkHttpClient();

        /**
         * 获取默认的OkHttpClient实例
         */
        private static OkHttpClient getOkHttpClient() {
            OkHttpClient.Builder okBuilder = new OkHttpClient.Builder();

//            okBuilder.authenticator(
//                    new Authenticator() {
//                        @Override
//                        public Request authenticate(Route route, Response response) {
//                            String credential = Credentials.basic("jesse", "password1");
//                            XLog.i("credential->" + credential);
//                            return response.request().newBuilder()
//                                    .header("Authorization", credential)
//                                    .build();
//                        }
//                    }
//            );

//            SSLSocketUtils ssl = SSLSocketUtils.getInstance();
//            okBuilder.hostnameVerifier(ssl.getHostnameVerifier());
//            okBuilder.sslSocketFactory(ssl.getSslSocketFactory(), ssl.getTrustManager());

            // 处理重试的机制
//            okBuilder.retryOnConnectionFailure(true);
//        okBuilder.addInterceptor(new RetryInterceptor(3));

            /* 最大缓存 50M，OKHttp会默认缓存GET请求 */
            int maxSize = 50 * 1024 * 1024;// 50MB
//            XLog.i("size:" + ConvertUtils.byte2FitSize(maxSize));
            File cacheDir = new File(FileUtils.getCacheDir(XLog.sContext), "/okhttp_cache");
            Cache cache = new Cache(cacheDir, maxSize);
            okBuilder.cache(cache);
            /*默认是支持get请求的数据缓存*/

//            okBuilder.addNetworkInterceptor(new RetryInterceptor(3));
            return okBuilder.build();
        }

        /**
         * 上传下载文件用的OkHttpClient实例
         */
        private static OkHttpClient sFileOkHttpClient = new OkHttpClient.Builder()
                .connectTimeout(1, TimeUnit.MINUTES)
                .readTimeout(1, TimeUnit.MINUTES)
                .writeTimeout(1, TimeUnit.MINUTES)
                .build();
    }
}